Константные указатели, указатели-константы и указатели на константы

Очень много ошибок в программировании связано с неправильным или непредсказуемым изменением переменных. Даже в простом приложении можно напутать порядок работы или забыть удалить ресурсы. В многопоточных приложениях обобщённые ресурсы становятся настоящей головной болью. При совместной разработке, если функции позволяют изменять значение переданного аргумента, рано или поздно кто-нибудь обязательно их изменит неправильным образом.

Один из методов борьбы с такими ошибками – это запрет модификации объекта. В си можно создавать такие указатели, которые могут изменять своё значение (то есть, их можно переприсваивать), но при этом они могут только читать содержимое памяти и не могут его изменять.

#include <conio.h>
#include <stdio.h>

void main() {
    const char* p = NULL;
    unsigned length = 0;
    char word[] = "Hello world!";

    p = word;
    while (*p++) {
        length++;
    }
    //Операция *p = '?' запрещена и приведёт к ошибке

    printf("length(\"%s\") = %d", word, length);
    _getch();
}

Использование константных указателей позволяет значительно обезопасить работу. Например, если мы используем функцию, и она принимает константный указатель, то мы уверены, что она не изменит наши данные.

#include <conio.h>
#include <stdio.h>

unsigned getSafeLength(const char *word) {
    const char *p = word;
    unsigned length = 0;
    if (word != NULL) {
        while (*p++) {
            length++;
        }
    }
    return length;
}

unsigned getUnsafeLength(char *word) {
    char *p = word;
    unsigned length = 0;
    if (word != NULL) {
        while (*p++) {
            //HAHAHA! EVIL ACTIVITY!
            if (*p == 'A') {
                *p = 'B';
            }
            length++;
        }
    }
    return length;
}

void main() {
    const char* p = NULL;
    unsigned length = 0;
    char word[] = "Hay guys! Right answer for #3 question is A";

    printf("length(\"%s\") = %d\n", word, getSafeLength(word));
    printf("length(\"%s\") = %d\n", word, getUnsafeLength(word));
    _getch();
}

Когда мы видим код программы, у нас есть возможность узнать, меняются ли переданные аргументы или нет. Но это уже требует времени. Если мы пользуемся чужими библиотеками, то у нас есть доступ только до документации или до h-файлов.

unsigned getSafeLength(const char *);
unsigned getUnsafeLength(char *);

Из определения функции видно, что первая функция не может изменить аргумента, о второй ничего не известно.

Старайтесь использовать константные указатели, где есть возможность.

Указатель может быть константой сам по себе. Это значит, что нельзя изменить содержимого указателя, но с помощью него можно получить доступ до содержимого объекта, на который он ссылается.

#include <conio.h>
#include <stdio.h>

void main() {
    int x = 100;
    int *const p = &x;

    printf("x = %d\n", x);
    *p = 200;
    printf("x = %d", x);
    //операция p = &? или p++ и т.п. запрещены

    _getch();
}

Можно комбинировать, и создать константный указатель константу. Возможно, это когда-нибудь пригодится.

#include <conio.h>
#include <stdio.h>

void main() {
    int x = 100;
    const int *const p = &x;

    printf("x = %d\n", x);
    printf("x = %d", *p);

    _getch();
}

Можно также хранить указатель константы. Так как это константа, то необходимо, чтобы указатель тоже был константным.

#include <conio.h>
#include <stdio.h>

void main() {
    const int x = 100;
    const int *p;

    p = &x;

    printf("x = %d\n", x);
    printf("x = %d", *p);

    _getch();
}

Ну и самый жуткий пример: константный указатель константа на константу. Область применения: сферическое программирование в вакууме.

Заметьте, const char *p и char const *p ничем не отличаются. Каких-либо условий, когда как писать далее делать не будем.